<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Force layout</title>
  <style>
    .link {
      stroke: #000;
      stroke-width: 1.5px;
    }
    .unfixedNode {
      cursor: move;
      fill: #ccc;
      stroke: #000;
      stroke-width: 1.5px;
    }
    .fixedNode {
      cursor: move;
      fill: red;
      stroke: #000;
      stroke-width: 1.5px;
    }
  </style>
</head>
<body>
  <script src="https://d3js.org/d3.v5.min.js"></script>
  <script>
  	// fixed是固定坐标的节点，unfixed是没有固定坐标的节点。
    var graph ={
      "nodes": [
        {"id": 0, "category": "unfixed"},
        {"id": 1, "category": "ufixed"},
        {"id": 2, "category": "unfixed"},
        {"id": 3, "category": "fixed","x": 467, "y": 314},
        {"id": 4, "category": "unfixed"},
        {"id": 5, "category": "fixed","x": 425, "y": 207},
        {"id": 6, "category": "unfixed"},
        {"id": 7, "category": "unfixed"},
        {"id": 8, "category": "unfixed"},
        {"id": 9, "category": "fixed","x": 539, "y": 222},
        {"id": 10, "category": "unfixed"},
        {"id": 11, "category": "unfixed"},
        {"id": 12, "category": "unfixed"}
      ],
      "links": [
        {"source":  0, "target":  3},
        {"source":  1, "target":  3},
        {"source":  2, "target":  3},
        {"source":  3, "target":  4},
        {"source":  4, "target":  5},
        {"source":  5, "target":  6},
        {"source":  5, "target":  7},
        {"source":  5, "target":  8},
        {"source":  9, "target":  4},
        {"source":  9, "target":  11},
        {"source":  9, "target":  10},
        {"source":  9, "target":  12},
        {"source":  8, "target":  3},
      ]
    }

    var nodesCopy = JSON.parse(JSON.stringify(graph.nodes));

    var width = 960,
      height = 500;

    var force = d3.forceSimulation()
      .force("charge", d3.forceManyBody())
      .force("center", d3.forceCenter(width / 2, height / 2))
      .on("tick", tick);

    force
      .nodes(graph.nodes)
      .force("link", d3.forceLink(graph.links).id(function(d) {
      	return d.id;
      }));

    var svg = d3.select("body").append("svg")
      .attr("width", width)
      .attr("height", height);

    var link = svg.selectAll(".link"),
      node = svg.selectAll(".node");

    link = link.data(graph.links)
      .enter().append("line")
      .attr("class", "link");


    node = node.data(graph.nodes)
      .enter().append("circle")
      .attr("class", "node")
      .attr("class", d => {
        if (d.category == "fixed") {
          return "fixedNode";
        } else {
          return "unfixedNode";
        }
      })
      .attr("r", 12)
      .call(d3.drag()
        .on("start", dragstart)
        .on("drag", dragged)
        .on("end", dragended));

    node.append("title")
      .text(d => d.id);


    function tick() {
      node.attr("cx", function (d) {
        if (d.category == 'fixed') {
          d.fx = nodesCopy[d.id].x;
        }
        return d.x;
      })
        .attr("cy", function (d) {
          if (d.category == 'fixed') {
            d.fy = nodesCopy[d.id].y;
          };
          return d.y;
        });
      link.attr("x1", function (d) {
        return d.source.x;
      })
        .attr("y1", function (d) {
          return d.source.y;
        })
        .attr("x2", function (d) {
          return d.target.x;
        })
        .attr("y2", function (d) {
          return d.target.y;
        });
    }

    function dragstart(d) {
      if (!d3.event.active) {
        force.alphaTarget(.1).restart();
      }
      d.fx = d.x;
      d.fy = d.y;
    }

    function dragged(d) {
      d.fx = d3.event.x;
      d.fy = d3.event.y;
    }

    function dragended(d) {
      if (!d3.event.active) {
        force.alphaTarget(0);
      }
      d.fx = null;
      d.fy = null;
    }

    force.on("end", function () {
      console.log(graph.nodes)
    })
  </script>
</body>
</html>
